package config

import (
	"bufio"
	"encoding/json"
	"errors"
	"fmt"
	"os"
	"path/filepath"
	"strings"
)

const (
	defaultJSONName = ".cos-config.json"
	legacyEnvName   = ".cos-config"
	currentDirEnv   = ".env"
	maskReplacement = "***"
	fieldSecretID   = "COS_SECRET_ID"
	fieldSecretKey  = "COS_SECRET_KEY"
	fieldBucket     = "COS_BUCKET"
	fieldRegion     = "COS_REGION"

	ProviderCOS = "cos"
	ProviderS3  = "s3"
)

// Config holds credential information for cloud storage services
type Config struct {
	Name          string `json:"name"`                      // 配置名称
	Provider      string `json:"provider"`                  // 服务提供者 (cos 或 s3)
	SecretID      string `json:"secret_id"`                 // Access Key ID
	SecretKey     string `json:"secret_key"`                // Secret Access Key
	Bucket        string `json:"bucket"`
	Region        string `json:"region"`
	Endpoint      string `json:"endpoint,omitempty"`        // 可选的自定义端点
	UseCLIUpload  bool   `json:"use_cli_upload,omitempty"`
	CLIPath       string `json:"cli_path,omitempty"`
	UseCLIWSL     bool   `json:"use_cli_wsl,omitempty"`
	CLIWSLCommand string `json:"cli_wsl_command,omitempty"`
}

// Configurations 包含多个命名配置
type Configurations struct {
	ActiveConfig string   `json:"active_config"`
	Configs      []Config `json:"configs"`
}

// IsValid reports whether all required credential fields are present.
func (c *Config) IsValid() bool {
	if c == nil {
		return false
	}

	// 基本字段检查
	valid := c.SecretID != "" &&
		c.SecretKey != "" &&
		c.Bucket != "" &&
		c.Region != ""

	// 对于S3 provider，允许通过endpoint指定自定义端点
	if c.Provider == ProviderS3 && c.Endpoint != "" {
		return valid
	}

	return valid
}

// IsCOS checks if the config is for Tencent COS
func (c *Config) IsCOS() bool {
	return c != nil && (c.Provider == "" || c.Provider == ProviderCOS)
}

// IsS3 checks if the config is for AWS S3 or S3-compatible service
func (c *Config) IsS3() bool {
	return c != nil && c.Provider == ProviderS3
}

// Merge fills empty fields in the receiver with values from the provided config.
func (c *Config) Merge(other *Config) {
	if c == nil || other == nil {
		return
	}
	if c.SecretID == "" && other.SecretID != "" {
		c.SecretID = other.SecretID
	}
	if c.SecretKey == "" && other.SecretKey != "" {
		c.SecretKey = other.SecretKey
	}
	if c.Bucket == "" && other.Bucket != "" {
		c.Bucket = other.Bucket
	}
	if c.Region == "" && other.Region != "" {
		c.Region = other.Region
	}
	if c.Name == "" && other.Name != "" {
		c.Name = other.Name
	}
	if c.Provider == "" && other.Provider != "" {
		c.Provider = other.Provider
	}
	if c.Endpoint == "" && other.Endpoint != "" {
		c.Endpoint = other.Endpoint
	}
	if c.CLIPath == "" && other.CLIPath != "" {
		c.CLIPath = other.CLIPath
	}
	if !c.UseCLIWSL && other.UseCLIWSL {
		c.UseCLIWSL = true
	}
	if c.CLIWSLCommand == "" && other.CLIWSLCommand != "" {
		c.CLIWSLCommand = other.CLIWSLCommand
	}
}

// DefaultJSONPath returns ~/.cos-config.json. Caller should handle errors.
func DefaultJSONPath() (string, error) {
	home, err := os.UserHomeDir()
	if err != nil {
		return "", fmt.Errorf("resolve home dir: %w", err)
	}
	return filepath.Join(home, defaultJSONName), nil
}

// LegacyEnvPath returns ~/.cos-config for compatibility with older releases.
func LegacyEnvPath() (string, error) {
	home, err := os.UserHomeDir()
	if err != nil {
		return "", fmt.Errorf("resolve home dir: %w", err)
	}
	return filepath.Join(home, legacyEnvName), nil
}

// LoadFromJSON loads JSON config from disk and merges it into the provided config.
func LoadFromJSON(path string, cfg *Config) error {
	data, err := os.ReadFile(path)
	if err != nil {
		return err
	}

	// First try to load as new multi-config format
	var configs Configurations
	if err := json.Unmarshal(data, &configs); err == nil && len(configs.Configs) > 0 {
		// Find active config or use first one
		for _, c := range configs.Configs {
			if c.Name == configs.ActiveConfig || configs.ActiveConfig == "" {
				cfg.Merge(&c)
				cfg.UseCLIUpload = c.UseCLIUpload
				return nil
			}
		}
		// Fallback to first config
		cfg.Merge(&configs.Configs[0])
		cfg.UseCLIUpload = configs.Configs[0].UseCLIUpload
		return nil
	}

	// Fallback to old single config format
	var fileCfg Config
	if err := json.Unmarshal(data, &fileCfg); err != nil {
		return err
	}

	cfg.Merge(&fileCfg)
	cfg.UseCLIUpload = fileCfg.UseCLIUpload
	cfg.CLIPath = strings.TrimSpace(fileCfg.CLIPath)
	cfg.UseCLIWSL = fileCfg.UseCLIWSL
	cfg.CLIWSLCommand = strings.TrimSpace(fileCfg.CLIWSLCommand)
	return nil
}

// SaveAsJSON writes configuration data to the supplied path, creating directories as needed.
func SaveAsJSON(path string, cfg *Config) error {
	if cfg == nil {
		return errors.New("config is nil")
	}

	// Create parent directories if needed
	if dir := filepath.Dir(path); dir != "." && dir != "/" {
		if err := os.MkdirAll(dir, 0755); err != nil {
			return fmt.Errorf("create parent directories: %w", err)
		}
	}

	// Save as single config for backward compatibility
	data, err := json.MarshalIndent(cfg, "", "  ")
	if err != nil {
		return fmt.Errorf("marshal config: %w", err)
	}

	if err := os.WriteFile(path, data, 0600); err != nil {
		return fmt.Errorf("write file: %w", err)
	}

	return nil
}

// SaveConfigsAsJSON saves multiple configurations to the supplied path
func SaveConfigsAsJSON(path string, configs *Configurations) error {
	if configs == nil {
		return errors.New("configs is nil")
	}

	// Create parent directories if needed
	if dir := filepath.Dir(path); dir != "." && dir != "/" {
		if err := os.MkdirAll(dir, 0755); err != nil {
			return fmt.Errorf("create parent directories: %w", err)
		}
	}

	data, err := json.MarshalIndent(configs, "", "  ")
	if err != nil {
		return fmt.Errorf("marshal configs: %w", err)
	}

	if err := os.WriteFile(path, data, 0600); err != nil {
		return fmt.Errorf("write file: %w", err)
	}

	return nil
}

// Load assembles configuration data by respecting JSON, legacy files, and env overrides.
func Load() (*Config, error) {
	jsonPath, err := DefaultJSONPath()
	if err != nil {
		return nil, err
	}
	return LoadWithPath(jsonPath)
}

// LoadWithPath mirrors Load but allows overriding the JSON config path.
func LoadWithPath(jsonPath string) (*Config, error) {
	cfg := &Config{}

	if err := LoadFromJSON(jsonPath, cfg); err != nil {
		if !errors.Is(err, os.ErrNotExist) {
			return nil, fmt.Errorf("load json config: %w", err)
		}
	}

	if err := mergeLegacySources(cfg); err != nil {
		return nil, err
	}

	applyEnvOverrides(cfg)

	if cfg.Provider == "" {
		cfg.Provider = ProviderCOS
	}

	return cfg, nil
}

// loadLegacyEnv parses key=value lines from the legacy config file.
func loadLegacyEnv(path string, cfg *Config) error {
	file, err := os.Open(path)
	if err != nil {
		return err
	}
	defer file.Close()

	scanner := bufio.NewScanner(file)
	for scanner.Scan() {
		line := strings.TrimSpace(scanner.Text())
		if line == "" || strings.HasPrefix(line, "#") {
			continue
		}

		parts := strings.SplitN(line, "=", 2)
		if len(parts) != 2 {
			continue
		}

		key := strings.TrimSpace(parts[0])
		value := strings.TrimSpace(parts[1])
		value = strings.Trim(value, `"'`)

		switch key {
		case fieldSecretID:
			if cfg.SecretID == "" {
				cfg.SecretID = value
			}
		case fieldSecretKey:
			if cfg.SecretKey == "" {
				cfg.SecretKey = value
			}
		case fieldBucket:
			if cfg.Bucket == "" {
				cfg.Bucket = value
			}
		case fieldRegion:
			if cfg.Region == "" {
				cfg.Region = value
			}
		}
	}

	return scanner.Err()
}

// loadLocalEnv loads configuration from a local .env file.
func loadLocalEnv(filename string, cfg *Config) error {
	return loadLegacyEnv(filename, cfg)
}

// mergeLegacySources merges ~/.cos-config and local .env files.
func mergeLegacySources(cfg *Config) error {
	legacyPath, err := LegacyEnvPath()
	if err != nil {
		return fmt.Errorf("get legacy path: %w", err)
	}

	if err := loadLegacyEnv(legacyPath, cfg); err != nil && !errors.Is(err, os.ErrNotExist) {
		return fmt.Errorf("load legacy config: %w", err)
	}

	if err := loadLocalEnv(currentDirEnv, cfg); err != nil && !errors.Is(err, os.ErrNotExist) {
		return fmt.Errorf("load local .env: %w", err)
	}

	return nil
}

// applyEnvOverrides overwrites config fields with explicit environment variables.
func applyEnvOverrides(cfg *Config) {
	if cfg == nil {
		return
	}

	if v := strings.TrimSpace(os.Getenv(fieldSecretID)); v != "" {
		cfg.SecretID = v
	}
	if v := strings.TrimSpace(os.Getenv(fieldSecretKey)); v != "" {
		cfg.SecretKey = v
	}
	if v := strings.TrimSpace(os.Getenv(fieldBucket)); v != "" {
		cfg.Bucket = v
	}
	if v := strings.TrimSpace(os.Getenv(fieldRegion)); v != "" {
		cfg.Region = v
	}
}
